本文同步刊登於 k2r2bai.com Blog。
隨著團隊越來越多地在生產環境使用 Kubernetes 管理雲原生應用程式,我們必須考量在各種故障下,Kubernetes 能正常運行的情況,比如說:在流量高峰期間,將工作負載分散到更多節點或轉往公有雲、跨多個 Availability Zones/Regions 部署、建構高可靠(Highly Available)架構等等要求。其中高可靠架構在昨天的淺談 Kubernetes 高可靠架構文章中,簡單地複習了高可靠架構。而今天將說明如何實現與利用 kubeadm 建立一座架構大致如下圖所示的 HA 叢集。
在開始建立前,我們先簡單瞭解每個主節點要執行元件,以及這些元件如何完成高可靠架構。
Cluster Size | Majority | Failure Tolerance |
---|---|---|
1 | 1 | 0 |
2 | 2 | 0 |
3 | 2 | 1 |
4 | 3 | 1 |
5 | 3 | 2 |
6 | 4 | 2 |
7 | 4 | 3 |
計算故障容許節點數為
(N/2)+1
,其中 N 為叢集大小。
(圖片擷取自: Kubecon NA 2018 - Highly Available Kubernetes Clusters - Best Practices)
(圖片擷取自: Kubecon NA 2018 - Highly Available Kubernetes Clusters - Best Practices)
(圖片擷取自: Kubecon NA 2018 - Highly Available Kubernetes Clusters - Best Practices)
另外由於 API servers 需要提供 VIP 與負載平衡器,因此必須在所有主節點上額外安裝以下元件來達到需求。
簡單了解完實現方式後,下一小節將說明如何利用 kubeadm 來建構 Kubernetes HA 叢集。
選用 kubeadm 是因為方便手動做測試,且 kubeadm HA 功能在 v1.15 版本進入了 Beta 階段,因此值得大家嘗試看看。當然過程中,若節點數過多的話,建議搭配 Ansible(or Puppet, SaltStack) 這類工具進行。
本部分將透過 Kubeadm 來部署 Kubernetes v1.15 版本的 High Availability 叢集,而本安裝主要是參考官方文件中的 Creating Highly Available Clusters with kubeadm 內容來進行,這邊將透過 HAProxy 與 Keepalived 的結合來實現控制面的 Load Balancer 與 VIP。
Kubernetes 部署的版本資訊:
Kubernetes 部署的網路資訊:
本教學採用以下節點數與機器規格進行部署裸機(Bare-metal),作業系統採用Ubuntu 18.04+
進行測試:
IP Address | Hostname | CPU | Memory | Role |
---|---|---|---|---|
172.22.132.11 | k8s-m1 | 4 | 16G | Master |
172.22.132.12 | k8s-m2 | 4 | 16G | Master |
172.22.132.13 | k8s-m3 | 4 | 16G | Master |
172.22.132.21 | k8s-n1 | 4 | 16G | Node |
172.22.132.22 | k8s-n2 | 4 | 16G | Node |
172.22.132.32 | k8s-g2 | 4 | 16G | Node |
另外所有 Master 節點將透過 Keepalived 提供一個 Virtual IP 172.22.132.10
作為使用。
- 所有操作全部用
root
使用者進行,主要方便部署用。
開始部署叢集前需先確保以下條件已達成:
所有節點
彼此網路互通,並且k8s-m1
SSH 登入其他節點為 passwdless,由於過程中很多會在某台節點(k8s-m1
)上以 SSH 複製與操作其他節點。$ systemctl stop firewalld && systemctl disable firewalld
$ setenforce 0
$ vim /etc/selinux/config
SELINUX=disabled
關閉是為了方便安裝使用,若有需要防火牆可以參考 Required ports 來設定。
所有節點
需要安裝 Docker CE 版本的容器引擎:$ curl -fsSL https://get.docker.com/ | sh
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
$ echo 'deb http://apt.kubernetes.io/ kubernetes-xenial main' | tee /etc/apt/sources.list.d/kubernetes.list
所有節點
需要設定以下系統參數。$ cat <<EOF | tee /etc/sysctl.d/k8s.conf
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
$ sysctl -p /etc/sysctl.d/k8s.conf
關於
bridge-nf-call-iptables
的啟用,主要取決於是否將容器連接到Linux bridge
或使用其他一些機制(如 SDN vSwitch)。
所有節點
利用以下指令關閉:$ swapoff -a && sysctl -w vm.swappiness=0
# 不同機器會有差異
$ sed '/swap.img/d' -i /etc/fstab
- 記得
/etc/fstab
也要註解掉SWAP
掛載。- 關閉 Swap 是避免 Kubernetes Pod 不會因為使用到 Swap 而影響效能,另一方面可以讓 OOM Killer 正常運作。
本節將說明如何部署與設定 Kubernetes Master 節點中的各元件。
在開始部署master
節點元件前,請先安裝好 kubeadm、kubelet 等套件,並建立/etc/kubernetes/manifests/
目錄存放 Static Pod 的 YAML 檔:
$ export KUBE_VERSION="1.15.4"
$ apt-get update && apt-get install -y kubelet=${KUBE_VERSION}-00 kubeadm=${KUBE_VERSION}-00 kubectl=${KUBE_VERSION}-00
$ apt-mark hold kubeadm kubectl kubelet
$ mkdir -p /etc/kubernetes/manifests/
完成後,依照下面小節完成部署。
本節將說明如何建立 HAProxy 來提供 Kubernetes API Server 的負載平衡。在所有master
節點的/etc/haproxy/
目錄:
$ mkdir -p /etc/haproxy/
接著在所有master
節點新增/etc/haproxy/haproxy.cfg
設定檔,並加入以下內容:
$ cat <<EOF > /etc/haproxy/haproxy.cfg
global
log 127.0.0.1 local0
log 127.0.0.1 local1 notice
tune.ssl.default-dh-param 2048
defaults
log global
mode http
option dontlognull
timeout connect 5000ms
timeout client 600000ms
timeout server 600000ms
listen stats
bind :9090
mode http
balance
stats uri /haproxy_stats
stats auth admin:admin123
stats admin if TRUE
frontend kube-apiserver-https
mode tcp
bind :8443
default_backend kube-apiserver-backend
backend kube-apiserver-backend
mode tcp
balance roundrobin
stick-table type ip size 200k expire 30m
stick on src
server apiserver1 172.22.132.11:6443 check
server apiserver2 172.22.132.12:6443 check
server apiserver3 172.22.132.13:6443 check
EOF
這邊會綁定
8443
作為 API Server 的 Proxy。
接著在新增一個路徑為/etc/kubernetes/manifests/haproxy.yaml
的 YAML 檔來提供 HAProxy 的 Static Pod 部署,其內容如下:
$ cat <<EOF > /etc/kubernetes/manifests/haproxy.yaml
kind: Pod
apiVersion: v1
metadata:
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ""
labels:
component: haproxy
tier: control-plane
name: kube-haproxy
namespace: kube-system
spec:
hostNetwork: true
priorityClassName: system-cluster-critical
containers:
- name: kube-haproxy
image: docker.io/haproxy:1.7-alpine
resources:
requests:
cpu: 100m
volumeMounts:
- name: haproxy-cfg
readOnly: true
mountPath: /usr/local/etc/haproxy/haproxy.cfg
volumes:
- name: haproxy-cfg
hostPath:
path: /etc/haproxy/haproxy.cfg
type: FileOrCreate
EOF
接下來將新增另一個 YAML 來提供部署 Keepalived。
本節將說明如何建立 Keepalived 來提供 Kubernetes API Server 的 VIP。在所有master
節點新增一個路徑為/etc/kubernetes/manifests/keepalived.yaml
的 YAML 檔來提供 HAProxy 的 Static Pod 部署,其內容如下:
$ cat <<EOF > /etc/kubernetes/manifests/keepalived.yaml
kind: Pod
apiVersion: v1
metadata:
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ""
labels:
component: keepalived
tier: control-plane
name: kube-keepalived
namespace: kube-system
spec:
hostNetwork: true
priorityClassName: system-cluster-critical
containers:
- name: kube-keepalived
image: docker.io/osixia/keepalived:2.0.17
env:
- name: KEEPALIVED_VIRTUAL_IPS
value: 172.22.132.10
- name: KEEPALIVED_INTERFACE
value: enp3s0
- name: KEEPALIVED_UNICAST_PEERS
value: "#PYTHON2BASH:['172.22.132.11', '172.22.132.12', '172.22.132.13']"
- name: KEEPALIVED_PASSWORD
value: d0cker
- name: KEEPALIVED_PRIORITY
value: "100"
- name: KEEPALIVED_ROUTER_ID
value: "51"
resources:
requests:
cpu: 100m
securityContext:
privileged: true
capabilities:
add:
- NET_ADMIN
EOF
KEEPALIVED_VIRTUAL_IPS
:Keepalived 提供的 VIPs。KEEPALIVED_INTERFACE
:VIPs 綁定的網卡。KEEPALIVED_UNICAST_PEERS
:其他 Keepalived 節點的單點傳播 IP。KEEPALIVED_PASSWORD
: Keepalived auth_type 的 Password。KEEPALIVED_PRIORITY
:指定了備援發生時,接手的介面之順序,數字越小,優先順序越高。這邊k8s-m1
設為 100,其餘為150
。KEEPALIVED_ROUTER_ID
:一組 Keepalived instance 的數字識別子。
首先在k8s-m1
節點建立kubeadm-config.yaml
的 Kubeadm Master Configuration 檔:
$ cat <<EOF > kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
kubernetesVersion: v1.15.4
controlPlaneEndpoint: "172.22.132.10:8443"
networking:
podSubnet: "10.244.0.0/16"
EOF
controlPlaneEndpoint
填入 VIPs 與 bind port。
新增完後,透過 kubeadm 來初始化 control plane:
$ kubeadm init --config=kubeadm-config.yaml --upload-certs
...
You can now join any number of the control-plane node running the following command on each as root:
kubeadm join 172.22.132.10:8443 --token qawtjn.l0bpc3o12fef33t5 \
--discovery-token-ca-cert-hash sha256:7310e2e34b47214eba2be7a44375ea588a1d59d3126ac11759853d59fa76fadc \
--control-plane --certificate-key 6b9fbbac56a7af8576d8c7f98e44d5d78984c7331ca6d41a066d05c3d3795cc7
Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 172.22.132.10:8443 --token qawtjn.l0bpc3o12fef33t5 \
--discovery-token-ca-cert-hash sha256:7310e2e34b47214eba2be7a44375ea588a1d59d3126ac11759853d59fa76fadc
請記下來 join 節點資訊,方便後面使用。若忘記的話,可以用
kubeadm token
指令重新取得。
經過一段時間完成後,接著透過 netstat 檢查是否正常啟動服務:
$ netstat -ntlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 172.22.132.10:8443 0.0.0.0:* LISTEN 11218/haproxy
tcp 0 0 0.0.0.0:9090 0.0.0.0:* LISTEN 11218/haproxy
tcp 0 0 127.0.0.1:44551 0.0.0.0:* LISTEN 9237/kubelet
tcp 0 0 127.0.0.1:10248 0.0.0.0:* LISTEN 9237/kubelet
tcp 0 0 127.0.0.1:10249 0.0.0.0:* LISTEN 11669/kube-proxy
tcp 0 0 172.22.132.11:2379 0.0.0.0:* LISTEN 10367/etcd
tcp 0 0 172.22.132.11:2380 0.0.0.0:* LISTEN 10367/etcd
tcp 0 0 127.0.0.1:10257 0.0.0.0:* LISTEN 10460/kube-controll
tcp 0 0 127.0.0.1:10259 0.0.0.0:* LISTEN 10615/kube-schedule
經過一段時間完成後,執行以下指令來使用 kubeconfig:
$ mkdir -p $HOME/.kube
$ cp -rp /etc/kubernetes/admin.conf $HOME/.kube/config
$ chown $(id -u):$(id -g) $HOME/.kube/config
透過 kubectl 檢查 Kubernetes 叢集狀況:
$ kubectl get no
NAME STATUS ROLES AGE VERSION
k8s-m1 NotReady master 31s v1.15.4
$ kubectl get cs
NAME STATUS MESSAGE ERROR
scheduler Healthy ok
controller-manager Healthy ok
etcd-0 Healthy {"health":"true"}
接著部署 Calico CNI plugin:
$ wget https://docs.projectcalico.org/v3.8/manifests/calico.yaml
$ sed -i 's/192.168.0.0\/16/10.244.0.0\/16/g' calico.yaml
$ kubectl apply -f calico.yaml
完成後,透過 kubectl 來查看 kube-system 的 Pod 建立狀況:
$ kubectl -n kube-system get po
NAME READY STATUS RESTARTS AGE
calico-kube-controllers-65b8787765-ckd7b 1/1 Running 0 8m8s
calico-node-l9wh9 1/1 Running 0 8m8s
coredns-5c98db65d4-89wq5 1/1 Running 0 9m14s
coredns-5c98db65d4-lmvvn 1/1 Running 0 9m14s
etcd-k8s-m1 1/1 Running 0 8m24s
kube-apiserver-k8s-m1 1/1 Running 0 8m17s
kube-controller-manager-k8s-m1 1/1 Running 0 8m26s
kube-haproxy-k8s-m1 1/1 Running 0 9m30s
kube-keepalived-k8s-m1 1/1 Running 0 8m13s
kube-proxy-g7clj 1/1 Running 0 9m14s
kube-scheduler-k8s-m1 1/1 Running 0 8m41s
到這邊k8s-m1
就完成部署了,接著我們要將k8s-m2
與k8s-m3
以控制平面節點加入現有叢集。
在 kubeadm v1.15 版本中,提供了自動配置 HA 的機制,因此只需要在其他主節點執行以下指令即可:
$ kubeadm join 172.22.132.10:8443 --token qawtjn.l0bpc3o12fef33t5 \
--discovery-token-ca-cert-hash sha256:7310e2e34b47214eba2be7a44375ea588a1d59d3126ac11759853d59fa76fadc \
--control-plane \
--certificate-key 6b9fbbac56a7af8576d8c7f98e44d5d78984c7331ca6d41a066d05c3d3795cc7 \
--ignore-preflight-errors=DirAvailable--etc-kubernetes-manifests
經過一段時間完成後,執行以下指令來使用 kubeconfig:
$ mkdir -p $HOME/.kube
$ cp -rp /etc/kubernetes/admin.conf $HOME/.kube/config
$ chown $(id -u):$(id -g) $HOME/.kube/config
透過 kubectl 檢查 Kubernetes 叢集狀況:
$ kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-m1 Ready master 10m v1.15.4
k8s-m2 Ready master 3m v1.15.4
k8s-m3 Ready master 74s v1.15.4
由於其他主節點加入方式一樣,所以
k8s-m3
節點請重複執行本節過程來加入。
本節將說明如何部署與設定 Kubernetes Node 節點中。在開始部署node
節點元件前,請先安裝好 kubeadm、kubelet 等套件:
$ export KUBE_VERSION="1.15.4"
$ apt-get update && apt-get install -y kubelet=${KUBE_VERSION}-00 kubeadm=${KUBE_VERSION}-00
$ apt-mark hold kubeadm kubelet
安裝好後,在所有node
節點透過 kubeadm 來加入節點:
$ kubeadm join 172.22.132.10:8443 \
--token qawtjn.l0bpc3o12fef33t5 \
--discovery-token-ca-cert-hash sha256:7310e2e34b47214eba2be7a44375ea588a1d59d3126ac11759853d59fa76fadc
...
This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.
Run 'kubectl get nodes' on the control-plane to see this node join the cluster.
當節點都完成後,進入任一台master
節點透過 kubectl 來檢查:
$ kubectl get no
NAME STATUS ROLES AGE VERSION
k8s-g1 Ready <none> 92s v1.15.4
k8s-g2 Ready <none> 3m33s v1.15.4
k8s-m1 Ready master 20m v1.15.4
k8s-m2 Ready master 13m v1.15.4
k8s-m3 Ready master 8m32s v1.15.4
k8s-n1 Ready <none> 3m30s v1.15.4
k8s-n2 Ready <none> 3m35s v1.15.4
$ kubectl get cs
NAME STATUS MESSAGE ERROR
scheduler Healthy ok
controller-manager Healthy ok
etcd-0 Healthy {"health": "true"}
kubeadm 的方式會讓狀態只顯示 etcd-0。
接著進入k8s-m1
節點測試叢集 HA 功能,這邊先關閉該節點:
$ sudo poweroff
接著進入到k8s-m2
節點,透過 kubectl 來檢查叢集是否能夠正常執行:
# 先檢查元件狀態
$ kubectl get cs
NAME STATUS MESSAGE ERROR
controller-manager Healthy ok
scheduler Healthy ok
etcd-0 Healthy {"health":"true"}
# 檢查 nodes 狀態
$ kubectl get no
NAME STATUS ROLES AGE VERSION
k8s-g1 Ready <none> 4m54s v1.15.4
k8s-g2 Ready <none> 6m55s v1.15.4
k8s-m1 NotReady master 55m v1.15.4
k8s-m2 Ready master 33m v1.15.4
k8s-m3 Ready master 11m v1.15.4
k8s-n1 Ready <none> 6m52s v1.15.4
k8s-n2 Ready <none> 6m57s v1.15.4
# 測試是否可以建立 Pod
$ kubectl run nginx --image nginx --restart=Never --port 80
$ kubectl expose pod nginx --port 80 --type NodePort
$ kubectl get po,svc
NAME READY STATUS RESTARTS AGE
pod/nginx 1/1 Running 0 27s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 56m
service/nginx NodePort 10.106.25.118 <none> 80:30461/TCP 21s
透過 cURL 檢查 NGINX 服務是否正常:
$ curl 172.22.132.10:30461
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
今天簡單透過 kubeadm 來實現 Kubernetes HA 架構,但大家肯定會發現這樣手動操作非常累,若要建立大量裸機節點時,將會有很多重複指令需要執行,因此這邊推薦結合 Ansible 這類工具進行,或者直接使用 Kubespray 來自動化部署 Kubernetes HA 環境。
但是這樣就能確保服務執行在 Kubernetes 上後,就都不會出事了嗎?
事實上,不光要針對 Kubernetes 做 HA 架構,我們還要對許多層面進行處理。
確認所有防火牆與 SELinux 已關閉。如 CentOS: 上面寫 UBUNTU 下面關閉指令卻是 CENTOS